package com.googlecode.svnj;

import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;

import com.googlecode.svnj.dav.Path;

public class XMLDeltaParser implements XMLStreamConstants {
    
    static enum Tag {
        UPDATE_REPORT("update-report"),
        RESOURCE_WALK("resource-walk"),
        RESOURCE("resource"),
        TARGET_REVISION("target-revision"),
        OPEN_DIRECTORY("open-directory"), 
         ADD_DIRECTORY("add-directory"),
         ABSENT_DIRECTORY("absent-directory"),
        OPEN_FILE("open-file"),
        ADD_FILE("add-file"),
        TXDELTA("txdelta"),
        ABSENT_FILE("absent-file"),
        DELETE_ENTRY("delete-entry"),
        FETCH_PROPS("fetch-props"),
        SET_PROPS("set-prop"),
        REMOVE_PROP("remove-prop"),
        FETCH_FILE("fetch-file"),
        PROP("prop"),
        REPO_UUID("repository-uuid"),
        MD5("md5-checksum"),
        VERSION_NAME("version-name"),
        CREATOR("creator-displayname"),
        CHECKED_IN("checked-in"),
        HREF("href")
      ;
      String name;
      Tag(String name) {
          this.name = name;
      }
      public String toString() { return name; }
    };
    
    static final Map<String,Tag> TAGS = new HashMap<String,Tag>();
    static {
        for (Tag t : Tag.class.getEnumConstants())
            TAGS.put(t.name, t);
    }
    private static Tag tag(String name) {
        Tag t = TAGS.get(name);
        if (t != null) return t;
        throw new IllegalArgumentException("unrecognized tag: '" + name + "' in " + TAGS);
    }
    XMLStreamReader stream;
    IDeltaEditor editor;
    
    public XMLDeltaParser(XMLStreamReader stream, IDeltaEditor editor) {
        this.stream = stream;
        this.editor = editor;
    }
    public void parse() throws Exception {
        int evt;
        Stack<String> dirs = new Stack<String>();
        boolean fileOpen = false, txDeltaInProgress = false;
        String last_md5 = null;
        while ((evt = stream.next()) != END_DOCUMENT) {
            if (evt == START_ELEMENT) {
                Tag t = tag(stream.getLocalName());
                switch (t) {
                case RESOURCE_WALK:
                    p("WARN: ignoring tag: " + t);
                    break;
                case UPDATE_REPORT:
                case RESOURCE:
                    break;
                case TARGET_REVISION:
                    editor.setTargetRevision(longAtt("rev"));
                    break;
                case OPEN_DIRECTORY: {
                    long rev = longAtt("rev");
                    if (dirs.size() == 0) {
                        editor.openRoot(rev);
                        dirs.push("");
                    } else {
                        String name = att("name");
                        editor.openDirectory(fullPath(dirs, name), rev);
                        dirs.push(name);
                    }
                    break;
                }
                case ADD_DIRECTORY: {
                    long copyfromRev = -1;
                    if (stream.getAttributeValue(null, "copyfrom-rev") != null)
                        copyfromRev = longAtt("copyfrom-rev");
                    editor.addDirectory(fullPath(dirs, att("name")), att("copyfrom-path"), copyfromRev);
                    break;
                }
                case ABSENT_DIRECTORY:
                    editor.absentDirectory(fullPath(dirs, att("name")));
                    break;
                case OPEN_FILE: {
                    String name = att("name");
                    fileOpen = true;
                    // add to full directory path we've seen so far
                    editor.openFile(fullPath(dirs, name), longAtt("rev"));
                    break;
                }
                case ADD_FILE: {
                    long copyfromRev = -1;
                    if (stream.getAttributeValue(null, "copyfrom-rev") != null)
                        copyfromRev = longAtt("copyfrom-rev");
                    fileOpen = true;
                    editor.addFile(fullPath(dirs, att("name")), att("copyfrom-path"), copyfromRev);
                    break;
                }
                case TXDELTA:
                    if (!txDeltaInProgress) {
                        txDeltaInProgress = true;
                        editor.beginTextDelta(null);
                    }
                    editor.appendTextDelta(stream.getElementText().trim());
                    break;
                case ABSENT_FILE:
                    editor.absentFile(fullPath(dirs, att("name")));
                    break;
                case DELETE_ENTRY: {
                    long rev = -1;
                    editor.deleteEntry(fullPath(dirs, att("name")), rev);
                }
                break;
                case FETCH_PROPS:
                case SET_PROPS: {
                    String name = att("name");
                    // FIXME: right to trim here?
                    String value = stream.getElementText().trim();
                    if (fileOpen)
                        editor.changeFileProp(name, value);
                    else
                        editor.changeDirProp(name, value);
                    break;
                }
                case REMOVE_PROP:
                    if (fileOpen)
                        editor.changeFileProp(att("name"), null);
                    else
                        editor.changeDirProp(att("name"), null);
                    break;
                    
                case MD5:
                    last_md5 = stream.getElementText();
                    if (last_md5 != null)
                        last_md5 = last_md5.trim();
                    if (txDeltaInProgress) {
                        txDeltaInProgress = false;
                        editor.endTextDelta();
                    }
                    break;
                case HREF: // not sure what to do with these
                    break; 
                /*case FETCH_FILE:*/
                /*case PROP:*/
                case REPO_UUID:
                case VERSION_NAME:
                case CREATOR:
                case CHECKED_IN:
                default:
                    //p("WARN: unhandled tag: " + t);
                }
            } else if (evt == END_ELEMENT) {
                String name = stream.getLocalName();
                //p("local=" + name + " " + stream.getName().getLocalPart());
                Tag t = tag(name);
                switch (t) {
                case OPEN_DIRECTORY:
                    if (dirs.size() > 0)
                        dirs.pop();
                    editor.closeDirectory();
                    break;
                case OPEN_FILE:
                case ADD_FILE:
                    fileOpen = false;
                    editor.closeFile(last_md5);
                    last_md5 = null;
                    break;
                }
            }
        }
        // put a fork in it!
        editor.closeEdit();
    }

    private String att(String name) {
        return stream.getAttributeValue(null, name);
    }
    private long longAtt(String name) {
        String s = stream.getAttributeValue(null, name);
        if (s == null)
            throw new RuntimeException(
                    "missing required attribute '" + name + "' for " + stream.getLocalName()
                    );
        return Long.parseLong(s);
    }
    private static String fullPath(Stack<String> dirs, String file) {
        if (dirs.size() == 0) return file;
        String path = "";
        for (String dir : dirs) {
            path += dir;
            if (dir.length() > 0)
                path += "/";
        }
        return path.length() > 0 ? Path.join(path, file) : file;
    }

    public static void main(String[] a) throws Exception {
        FileReader r = new FileReader("test/data/alphabet-update-r2.xml");
        XMLStreamReader xml = XMLUtil.initReader(r);
        IDeltaEditor ed;
        //ed = new NOOPDeltaEditor();
        ed = NativeEditor.createDebug();
        XMLDeltaParser parser = new XMLDeltaParser(xml, ed);
        parser.parse();
        p("done parse!");
        
    }
    private static void p(String s) {
        System.out.println("[XMLDeltaParser] " + s);
    }
}
